luci-app-dawn: Add configuration page
authorFeng Cheng <[email protected]>
Tue, 23 Jul 2024 01:58:58 +0000 (09:58 +0800)
committerPaul Donald <[email protected]>
Fri, 6 Dec 2024 00:05:07 +0000 (01:05 +0100)
Add the configuration page under the network path
Move hearing_map and network_overview pages to status path

Signed-off-by: Feng Cheng <[email protected]>
applications/luci-app-dawn/htdocs/luci-static/resources/view/dawn/hearing_map.js [deleted file]
applications/luci-app-dawn/htdocs/luci-static/resources/view/dawn/network_overview.js [deleted file]
applications/luci-app-dawn/htdocs/luci-static/resources/view/network/dawn.js [new file with mode: 0644]
applications/luci-app-dawn/htdocs/luci-static/resources/view/status/dawn/hearing_map.js [new file with mode: 0644]
applications/luci-app-dawn/htdocs/luci-static/resources/view/status/dawn/network_overview.js [new file with mode: 0644]
applications/luci-app-dawn/root/usr/share/luci/menu.d/luci-app-dawn.json

diff --git a/applications/luci-app-dawn/htdocs/luci-static/resources/view/dawn/hearing_map.js b/applications/luci-app-dawn/htdocs/luci-static/resources/view/dawn/hearing_map.js
deleted file mode 100644 (file)
index fdf840e..0000000
+++ /dev/null
@@ -1,105 +0,0 @@
-'use strict';
-'require uci';
-'require view';
-'require dawn.dawn-common as dawn';
-
-return view.extend({
-       handleSaveApply: null,
-       handleSave: null,
-       handleReset: null,
-
-       load: function() {
-               return Promise.all([
-                       dawn.isDawnRPCAvailable().then(function(isAvailable) {
-                               return ( isAvailable ? dawn.callDawnGetHearingMap() : null )
-                       }),
-                       dawn.isDawnRPCAvailable().then(function(isAvailable) {
-                               return ( isAvailable ? dawn.callDawnGetNetwork() : null )
-                       }),
-                       dawn.callHostHints()
-               ]);
-       },
-
-       render: function(data) {
-
-               const dawnHearingMapData = data[0];
-               const dawnNetworkData = data[1];
-               const hostHintsData = data[2];
-
-               let accessPointsHintsData = {};
-               let connectedClients = {};
-               for (let network in dawnNetworkData) {
-                       connectedClients[network] = [];
-                       let aps = Object.entries(dawnNetworkData[network]).map(function(ap) {
-                               accessPointsHintsData[ap[0]] = {name: ap[1].hostname};
-                               let clientData = Object.entries(ap[1]);
-                               for (let i = 0; i < clientData.length; i++) {
-                                       if (typeof clientData[i][1] === 'object') {
-                                               connectedClients[network].push(clientData[i][0]);
-                                       }
-                               }
-                       });
-               }
-
-               if (!dawnHearingMapData || !dawnNetworkData) {
-                       return dawn.getDawnServiceNotRunningErrorMessage();
-               }
-
-               const body = E([
-                       E('h2', _('Hearing Map'))
-               ]);
-
-               for (let network in dawnHearingMapData) {
-                       
-                       body.appendChild(
-                               E('h3', 'SSID: ' + network)
-                       );
-
-                       let hearing_map_table = E('table', { 'class': 'table cbi-section-table' }, [
-                               E('tr', { 'class': 'tr table-titles' }, [
-                                       E('th', { 'class': 'th' }, _('Client')),
-                                       E('th', { 'class': 'th' }, _('Access Point')),
-                                       E('th', { 'class': 'th' }, _('Frequency')),
-                                       E('th', { 'class': 'th' }, E('span', { 'data-tooltip': _('High Throughput') }, [ _('HT') ])),
-                                       E('th', { 'class': 'th' }, E('span', { 'data-tooltip': _('Very High Throughput') }, [ _('VHT') ])),
-                                       E('th', { 'class': 'th' }, _('Signal')),
-                                       E('th', { 'class': 'th' }, E('span', { 'data-tooltip': _('Received Channel Power Indication') }, [ _('RCPI') ])),
-                                       E('th', { 'class': 'th' }, E('span', { 'data-tooltip': _('Received Signal to Noise Indicator') }, [ _('RSNI') ])),
-                                       E('th', { 'class': 'th' }, _('Channel Utilization')),
-                                       E('th', { 'class': 'th' }, _('Connected to Network')),
-                                       E('th', { 'class': 'th' }, _('Score'))
-                               ])
-                       ]);
-
-                       let clients = Object.entries(dawnHearingMapData[network]).map(function(client) {
-                               
-                               return Object.entries(client[1]).map(function(ap) {
-
-                                       if (ap[1].freq != 0) {
-                                               return [
-                                                       dawn.getHostnameFromMAC(hostHintsData, client[0]),
-                                                       dawn.getHostnameFromMAC(accessPointsHintsData, ap[0]),
-                                                       dawn.getFormattedNumber(ap[1].freq, 3, 1000) + ' GHz (' + _('Channel') + ': ' + dawn.getChannelFromFrequency(ap[1].freq) + ')',
-                                                       dawn.getAvailableText(ap[1].ht_capabilities && ap[1].ht_support),
-                                                       dawn.getAvailableText(ap[1].vht_capabilities && ap[1].vht_support),
-                                                       ap[1].signal,
-                                                       ap[1].rcpi,
-                                                       ap[1].rsni,
-                                                       dawn.getFormattedNumber(ap[1].channel_utilization, 2, 2.55) + '%',
-                                                       dawn.getYesText(connectedClients[network].includes(client[0])),
-                                                       ap[1].score
-                                               ]
-                                       }
-                                       return undefined;
-                               })
-                               
-                       }).flat();
-                       clients = clients.filter(client => client !== undefined);
-
-                       cbi_update_table(hearing_map_table, clients, E('em', _('No clients connected.')));
-
-                       body.appendChild(hearing_map_table);
-               }
-               return body;
-       }
-});
diff --git a/applications/luci-app-dawn/htdocs/luci-static/resources/view/dawn/network_overview.js b/applications/luci-app-dawn/htdocs/luci-static/resources/view/dawn/network_overview.js
deleted file mode 100644 (file)
index a605858..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-'use strict';
-'require uci';
-'require view';
-'require dawn.dawn-common as dawn';
-
-return view.extend({
-       handleSaveApply: null,
-       handleSave: null,
-       handleReset: null,
-
-       load: function() {
-               return Promise.all([
-                       dawn.isDawnRPCAvailable().then(function(isAvailable) {
-                               return ( isAvailable ? dawn.callDawnGetNetwork() : null );
-                       }),
-                       dawn.callHostHints()
-               ]);
-       },
-
-       render: function(data) {
-
-               const dawnNetworkData = data[0];
-               const hostHintsData = data[1];
-
-               if (!dawnNetworkData) {
-                       return dawn.getDawnServiceNotRunningErrorMessage();
-               }
-
-               const body = E([
-                       E('h2', _('Network Overview'))
-               ]);
-
-               let client_table = {};
-
-               for (let network in dawnNetworkData) {
-                       
-                       body.appendChild(
-                               E('h3', 'SSID: ' + network)
-                       );
-
-                       let ap_table = E('table', { 'class': 'table cbi-section-table' }, [
-                               E('tr', { 'class': 'tr table-titles' }, [
-                                       E('th', { 'class': 'th left cbi-section-actions' }, _('Access Point')),
-                                       E('th', { 'class': 'th left cbi-section-actions' }, _('Interface')),
-                                       E('th', { 'class': 'th left cbi-section-actions' }, _('MAC')),
-                                       E('th', { 'class': 'th left cbi-section-actions' }, _('Utilization')),
-                                       E('th', { 'class': 'th left cbi-section-actions' }, _('Frequency')),
-                                       E('th', { 'class': 'th left cbi-section-actions' }, _('Stations Connected')),
-                                       E('th', { 'class': 'th left cbi-section-actions' }, E('span', { 'data-tooltip': _('High Throughput') }, [ _('HT') ])),
-                                       E('th', { 'class': 'th left cbi-section-actions' }, E('span', { 'data-tooltip': _('Very High Throughput') }, [ _('VHT') ])),
-                                       E('th', { 'class': 'th center cbi-section-actions' }, _('Clients')),
-                               ])
-                       ]);
-
-                       let aps = Object.entries(dawnNetworkData[network]).map(function(ap) {
-                               client_table[ap[0]] = E('table', { 'class': 'table cbi-section-table', 'style': 'display: table' }, [
-                                       E('tr', { 'class': 'tr table-titles' }, [
-                                               E('th', { 'class': 'th' }, _('Client')),
-                                               E('th', { 'class': 'th' }, E('span', { 'data-tooltip': _('High Throughput') }, [ _('HT') ])),
-                                               E('th', { 'class': 'th' }, E('span', { 'data-tooltip': _('Very High Throughput') }, [ _('VHT') ])),
-                                               E('th', { 'class': 'th' }, _('Signal'))
-                                       ])
-                               ]);
-
-                               let clients = [];
-                               let clientData = Object.entries(ap[1]);
-                               for (let i = 0; i < clientData.length; i++) {
-                                       if (typeof clientData[i][1] === 'object') {
-                                               clients.push([
-                                                       dawn.getHostnameFromMAC(hostHintsData ,clientData[i][0]),
-                                                       dawn.getAvailableText(clientData[i][1].ht),
-                                                       dawn.getAvailableText(clientData[i][1].vht),
-                                                       clientData[i][1].signal
-                                               ]);
-                                       }
-                               }
-
-                               cbi_update_table(client_table[ap[0]], clients, E('em', _('No clients connected.')));
-                       
-                               return [
-                                       ap[1].hostname,
-                                       ap[1].iface,
-                                       ap[0],
-                                       dawn.getFormattedNumber(ap[1].channel_utilization, 2, 2.55) + '%',
-                                       dawn.getFormattedNumber(ap[1].freq, 3, 1000) + ' GHz (' + _('Channel') + ': ' + dawn.getChannelFromFrequency(ap[1].freq) + ')',
-                                       ap[1].num_sta,
-                                       dawn.getAvailableText(ap[1].ht_support),
-                                       dawn.getAvailableText(ap[1].vht_support),
-                                       ap[1].num_sta > 0 ? client_table[ap[0]] : E('em', { 'style': 'display: inline' }, _('No clients connected.'))
-                               ]
-                       });
-
-                       cbi_update_table(ap_table, aps, E('em', _('No access points available.')));
-                       
-                       body.appendChild(ap_table);
-               }
-               return body;
-       }
-});
diff --git a/applications/luci-app-dawn/htdocs/luci-static/resources/view/network/dawn.js b/applications/luci-app-dawn/htdocs/luci-static/resources/view/network/dawn.js
new file mode 100644 (file)
index 0000000..05d0110
--- /dev/null
@@ -0,0 +1,496 @@
+'use strict';
+'require uci';
+'require view';
+'require form';
+
+var CBI80211StatusCode = form.ListValue.extend(/** @lends LuCI.form.ListValue.prototype */ {
+       __name__: 'CBI.80211StatusCode',
+
+       __init__: function() {
+               this.super('__init__', arguments);
+
+               this.value('0', 'SUCCESS');
+               this.value('1', 'UNSPECIFIED_FAILURE');
+               this.value('2', 'TDLS_WAKEUP_ALTERNATE');
+               this.value('3', 'TDLS_WAKEUP_REJECT');
+               this.value('5', 'SECURITY_DISABLED');
+               this.value('6', 'UNACCEPTABLE_LIFETIME');
+               this.value('7', 'NOT_IN_SAME_BSS');
+               this.value('10', 'CAPS_UNSUPPORTED');
+               this.value('11', 'REASSOC_NO_ASSOC');
+               this.value('12', 'ASSOC_DENIED_UNSPEC');
+               this.value('13', 'NOT_SUPPORTED_AUTH_ALG');
+               this.value('14', 'UNKNOWN_AUTH_TRANSACTION');
+               this.value('15', 'CHALLENGE_FAIL');
+               this.value('16', 'AUTH_TIMEOUT');
+               this.value('17', 'AP_UNABLE_TO_HANDLE_NEW_STA');
+               this.value('18', 'ASSOC_DENIED_RATES');
+               this.value('19', 'ASSOC_DENIED_NOSHORT');
+               this.value('22', 'SPEC_MGMT_REQUIRED');
+               this.value('23', 'PWR_CAPABILITY_NOT_VALID');
+               this.value('24', 'SUPPORTED_CHANNEL_NOT_VALID');
+               this.value('25', 'ASSOC_DENIED_NO_SHORT_SLOT_TIME');
+               this.value('27', 'ASSOC_DENIED_NO_HT');
+               this.value('28', 'R0KH_UNREACHABLE');
+               this.value('29', 'ASSOC_DENIED_NO_PCO');
+               this.value('30', 'ASSOC_REJECTED_TEMPORARILY');
+               this.value('31', 'ROBUST_MGMT_FRAME_POLICY_VIOLATION');
+               this.value('32', 'UNSPECIFIED_QOS_FAILURE');
+               this.value('33', 'DENIED_INSUFFICIENT_BANDWIDTH');
+               this.value('34', 'DENIED_POOR_CHANNEL_CONDITIONS');
+               this.value('35', 'DENIED_QOS_NOT_SUPPORTED');
+               this.value('37', 'REQUEST_DECLINED');
+               this.value('38', 'INVALID_PARAMETERS');
+               this.value('39', 'REJECTED_WITH_SUGGESTED_CHANGES');
+               this.value('40', 'INVALID_IE');
+               this.value('41', 'GROUP_CIPHER_NOT_VALID');
+               this.value('42', 'PAIRWISE_CIPHER_NOT_VALID');
+               this.value('43', 'AKMP_NOT_VALID');
+               this.value('44', 'UNSUPPORTED_RSN_IE_VERSION');
+               this.value('45', 'INVALID_RSN_IE_CAPAB');
+               this.value('46', 'CIPHER_REJECTED_PER_POLICY');
+               this.value('47', 'TS_NOT_CREATED');
+               this.value('48', 'DIRECT_LINK_NOT_ALLOWED');
+               this.value('49', 'DEST_STA_NOT_PRESENT');
+               this.value('50', 'DEST_STA_NOT_QOS_STA');
+               this.value('51', 'ASSOC_DENIED_LISTEN_INT_TOO_LARGE');
+               this.value('52', 'INVALID_FT_ACTION_FRAME_COUNT');
+               this.value('53', 'INVALID_PMKID');
+               this.value('54', 'INVALID_MDIE');
+               this.value('55', 'INVALID_FTIE');
+               this.value('56', 'REQUESTED_TCLAS_NOT_SUPPORTED');
+               this.value('57', 'INSUFFICIENT_TCLAS_PROCESSING_RESOURCES');
+               this.value('58', 'TRY_ANOTHER_BSS');
+               this.value('59', 'GAS_ADV_PROTO_NOT_SUPPORTED');
+               this.value('60', 'NO_OUTSTANDING_GAS_REQ');
+               this.value('61', 'GAS_RESP_NOT_RECEIVED');
+               this.value('62', 'STA_TIMED_OUT_WAITING_FOR_GAS_RESP');
+               this.value('63', 'GAS_RESP_LARGER_THAN_LIMIT');
+               this.value('64', 'REQ_REFUSED_HOME');
+               this.value('65', 'ADV_SRV_UNREACHABLE');
+               this.value('67', 'REQ_REFUSED_SSPN');
+               this.value('68', 'REQ_REFUSED_UNAUTH_ACCESS');
+               this.value('72', 'INVALID_RSNIE');
+               this.value('73', 'U_APSD_COEX_NOT_SUPPORTED');
+               this.value('74', 'U_APSD_COEX_MODE_NOT_SUPPORTED');
+               this.value('75', 'BAD_INTERVAL_WITH_U_APSD_COEX');
+               this.value('76', 'ANTI_CLOGGING_TOKEN_REQ');
+               this.value('77', 'FINITE_CYCLIC_GROUP_NOT_SUPPORTED');
+               this.value('78', 'CANNOT_FIND_ALT_TBTT');
+               this.value('79', 'TRANSMISSION_FAILURE');
+               this.value('80', 'REQ_TCLAS_NOT_SUPPORTED');
+               this.value('81', 'TCLAS_RESOURCES_EXCHAUSTED');
+               this.value('82', 'REJECTED_WITH_SUGGESTED_BSS_TRANSITION');
+               this.value('83', 'REJECT_WITH_SCHEDULE');
+               this.value('84', 'REJECT_NO_WAKEUP_SPECIFIED');
+               this.value('85', 'SUCCESS_POWER_SAVE_MODE');
+               this.value('86', 'PENDING_ADMITTING_FST_SESSION');
+               this.value('87', 'PERFORMING_FST_NOW');
+               this.value('88', 'PENDING_GAP_IN_BA_WINDOW');
+               this.value('89', 'REJECT_U_PID_SETTING');
+               this.value('92', 'REFUSED_EXTERNAL_REASON');
+               this.value('93', 'REFUSED_AP_OUT_OF_MEMORY');
+               this.value('94', 'REJECTED_EMERGENCY_SERVICE_NOT_SUPPORTED');
+               this.value('95', 'QUERY_RESP_OUTSTANDING');
+               this.value('96', 'REJECT_DSE_BAND');
+               this.value('97', 'TCLAS_PROCESSING_TERMINATED');
+               this.value('98', 'TS_SCHEDULE_CONFLICT');
+               this.value('99', 'DENIED_WITH_SUGGESTED_BAND_AND_CHANNEL');
+               this.value('100', 'MCCAOP_RESERVATION_CONFLICT');
+               this.value('101', 'MAF_LIMIT_EXCEEDED');
+               this.value('102', 'MCCA_TRACK_LIMIT_EXCEEDED');
+               this.value('103', 'DENIED_DUE_TO_SPECTRUM_MANAGEMENT');
+               this.value('104', 'ASSOC_DENIED_NO_VHT');
+               this.value('105', 'ENABLEMENT_DENIED');
+               this.value('106', 'RESTRICTION_FROM_AUTHORIZED_GDB');
+               this.value('107', 'AUTHORIZATION_DEENABLED');
+               this.value('112', 'FILS_AUTHENTICATION_FAILURE');
+               this.value('113', 'UNKNOWN_AUTHENTICATION_SERVER');
+       },
+});
+
+return view.extend({
+       load: function() {
+               return Promise.all([
+                       uci.load('dawn')
+               ]);
+       },
+
+       render: function(data) {
+               var m, s, o;
+               uci.get_first('dawn', 'local') != null || uci.add('dawn', 'local');
+               uci.get_first('dawn', 'hostapd') != null || uci.add('dawn', 'hostapd');
+               uci.get_first('dawn', 'network') != null || uci.add('dawn', 'network');
+
+               m = new form.Map('dawn',
+                       _('DAWN'),
+                       _('DAWN Form Configuration.'));
+
+               s = m.section(form.NamedSection, '@local[0]', 'local',
+                       _('Local'),
+               );
+
+               o = s.option(form.ListValue, 'loglevel',
+                       _('Log Level'),
+                       _('Verbosity of messages in syslog'),
+               );
+               o.value('0', _('Deeper tracing to fix bugs - for debugging'));
+               o.value('1', _('More info to help trace where algorithms may be going wrong - for debugging'));
+               o.value('2', _('Reporting on standard behaviour'));
+               o.value('3', _('Standard behaviour always worth reporting'));
+               o.value('4', _('Something appears wrong, but recoverable'));
+               o.value('5', _('Serious malfunction / unexpected behaviour'));
+
+
+               s = m.section(form.NamedSection, '@hostapd[0]', 'hostapd',
+                       _('Hostapd'),
+               );
+
+               o = s.option(form.Value, 'hostapd_dir',
+                       _('Hostapd dir'),
+                       _('Path to hostapd runtime information'),
+               );
+               o.default = '/var/run/hostapd';
+
+               s = m.section(form.NamedSection, '@network[0]', 'network',
+                       _('Network'),
+               );
+
+               // o = s.option(form.Value, 'bandwidth',
+               //      _('Bandwidth'),
+               // );
+
+               o = s.option(form.Value, 'broadcast_ip',
+                       _('Broadcast IP'),
+                       _('IP address for broadcast and multicast'),
+               );
+
+               o = s.option(form.Value, 'broadcast_port',
+                       _('Broadcast PORT'),
+                       _('IP port for broadcast and multicast'),
+               );
+               o.default = '1025';
+               o.placeholder = '1025';
+
+               // o = s.option(form.Value, 'collision_domain',
+               //      _('Collision domain'),
+               // );
+
+               // o = s.option(form.Value, 'iv',
+               //      _('Initialisation Vector (IV)'),
+               // );
+
+               o = s.option(form.ListValue, 'network_option',
+                       _('Network option'),
+                       _('Method of networking between DAWN instances'),
+               );
+               o.value('0', _('Broadcast'));
+               o.value('1', _('Multicast'));
+               o.value('2', _('TCP with UMDNS discovery'));
+               o.value('3', _('TCP w/out UMDNS discovery'));
+               o.default = '2';
+
+               o = s.option(form.Value, 'server_ip',
+                       _('Server IP'),
+                       _('IP address when not using UMDNS'),
+               );
+
+               // o = s.option(form.Value, 'shared_key',
+               //      _('Shared key'),
+               // );
+
+               o = s.option(form.Value, 'tcp_port',
+                       _('TCP port'),
+                       _('Port for TCP networking'),
+               );
+               o.default = '1026';
+               o.placeholder = '1026';
+
+               // o = s.option(form.Flag, 'use_symm_enc',
+               //      _('Use symm enc'),
+               //      _('Enable encryption of network traffic'),
+               // );
+               // o.default = '0';
+
+               s = m.section(form.NamedSection, '@times[0]', 'times',
+                       _('Times'),
+                       _('All timer values are in seconds. They are the main mechanism for DAWN collecting and managing much of the data that it relies on.'),
+               );
+
+               o = s.option(form.Value, 'con_timeout',
+                       _('Connection Timeout'),
+                       _('Timespan until a connection is seen as disconnected'),
+               );
+               o.placeholder = _('Default Value') + ': ' + '60';
+
+               o = s.option(form.Value, 'remove_ap',
+                       _('Remove AP'),
+                       _('Timer to remove expired AP entries from core data set'),
+               );
+               o.placeholder = _('Default Value') + ': ' + '460';
+
+               o = s.option(form.Value, 'remove_client',
+                       _('Remove Client'),
+                       _('Timer to remove expired client entries from core data set'),
+               );
+               o.placeholder = _('Default Value') + ': ' + '15';
+
+               o = s.option(form.Value, 'remove_probe',
+                       _('Remove Probe'),
+                       _('Timer to remove expired PROBE and BEACON entries from core data set'),
+               );
+               o.placeholder = _('Default Value') + ': ' + '30';
+
+               o = s.option(form.Value, 'update_beacon_reports',
+                       _('Update Beacon reports'),
+                       _('Timer to ask all connected clients for a new BEACON REPORT'),
+               );
+               o.placeholder = _('Default Value') + ': ' + '20';
+
+               o = s.option(form.Value, 'update_chan_util',
+                       _('Update Channel utilization'),
+                       _('Timer to get recent channel utilization figure for each local BSSID'),
+               );
+               o.placeholder = _('Default Value') + ': ' + '5';
+
+               o = s.option(form.Value, 'update_client',
+                       _('Update Client'),
+                       _('Timer to refresh local connection information and send revised NEIGHBOR REPORT to all clients'),
+               );
+               o.placeholder = _('Default Value') + ': ' + '10';
+
+               o = s.option(form.Value, 'update_hostapd',
+                       _('Update Hostapd'),
+                       _('Timer to (re-)register for hostapd messages for each local BSSID'),
+               );
+               o.placeholder = _('Default Value') + ': ' + '10';
+
+               o = s.option(form.Value, 'update_tcp_con',
+                       _('Update TCP connections'),
+                       _('Timer to refresh / remove the TCP connections to other DAWN instances found via uMDNS'),
+               );
+               o.placeholder = _('Default Value') + ': ' + '10';
+
+               s = m.section(form.NamedSection, 'global', 'metric',
+                       _('Global Metric'),
+               );
+
+               o = s.option(form.Value, 'bandwidth_threshold',
+                       _('Bandwidth Threshold (Mbits/s)'),
+                       _('Maximum reported AP-client bandwidth permitted when kicking. Set to zero to disable the check.'),
+               );
+               o.placeholder = _('Default Value') + ': ' + '6';
+
+               o = s.option(form.Value, 'chan_util_avg_period',
+                       _('Average channel utilization'),
+                       _('Number of sampling periods to average channel utilization values over'),
+               );
+               o.placeholder = _('Default Value') + ': ' + '3';
+
+               o = s.option(CBI80211StatusCode, 'deny_assoc_reason',
+                       _('Deny Association reason'),
+                       _('802.11 code used when ASSOCIATION is denied'),
+               );
+               o.placeholder = _('Default Value') + ': ' + 'AP_UNABLE_TO_HANDLE_NEW_STA';
+
+               o = s.option(CBI80211StatusCode, 'deny_auth_reason',
+                       _('Deny auth reason'),
+                       _('802.11 code used when AUTHENTICATION is denied'),
+               );
+               o.placeholder = _('Default Value') + ': ' + 'UNSPECIFIED_FAILURE';
+
+               o = s.option(form.Value, 'disassoc_nr_length',
+                       _('Disassociate Neighbor Report length'),
+                       _('Number of entries to include in a 802.11v DISASSOCIATE Neighbor Report'),
+               );
+               o.placeholder = _('Default Value') + ': ' + '6';
+
+               o = s.option(form.Value, 'duration',
+                       _('DURATION'),
+                       _('802.11k BEACON request DURATION parameter'),
+               );
+               o.placeholder = _('Default Value') + ': ' + '0';
+
+               o = s.option(form.Flag, 'eval_assoc_req',
+                       _('Evaluated Association Req'),
+                       _('Control whether ASSOCIATION frames are evaluated for rejection'),
+               );
+
+               o = s.option(form.Flag, 'eval_auth_req',
+                       _('Evaluated Auth Req'),
+                       _('Control whether AUTHENTICATION frames are evaluated for rejection'),
+               );
+
+               o = s.option(form.Flag, 'eval_probe_req',
+                       _('Evaluated Probe Req'),
+                       _('Control whether PROBE frames are evaluated for rejection'),
+               );
+
+               o = s.option(form.ListValue, 'kicking',
+                       _('Kicking'),
+                       _('Method to select clients to move to better AP'),
+               );
+               o.value('0', 'Disabled');
+               o.value('1', 'RSSI Comparison');
+               o.value('2', 'Absolute RSSI');
+               o.value('3', 'Both');
+               o.placeholder = _('Default Value') + ': ' + 'Both';
+
+               o = s.option(form.Value, 'kicking_threshold',
+                       _('Kicking Threshold'),
+                       _('Minimum score difference to consider kicking to alternate AP'),
+               );
+               o.placeholder = _('Default Value') + ': ' + '20';
+
+               o = s.option(form.Value, 'max_station_diff',
+                       _('Max Station Diff'),
+                       _('Number of connected stations to consider "better" for use_station_count'),
+               );
+               o.placeholder = _('Default Value') + ': ' + '1';
+
+               o = s.option(form.Value, 'min_number_to_kick',
+                       _('Min Number To Kick'),
+                       _('Number of consecutive times a client should be evaluated as ready to kick before actually doing it'),
+               );
+               o.placeholder = _('Default Value') + ': ' + '3';
+
+               o = s.option(form.Value, 'min_probe_count',
+                       _('Min Probe Count'),
+                       _('Number of times a client should retry PROBE before acceptance'),
+               );
+               o.placeholder = _('Default Value') + ': ' + '3';
+
+               o = s.option(form.Value, 'neighbors',
+                       _('Neighbors'),
+                       _('Space separated list of MACS to use in "static" AP Neighbor Report'),
+               );
+
+               o = s.option(form.Value, 'rrm_mode',
+                       _('RRM Mode'),
+                       _('Preferred order for using Passive, Active or Table 802.11k BEACON information'),
+               );
+               o.placeholder = _('Default Value') + ': ' + 'PAT';
+
+               o = s.option(form.ListValue, 'set_hostapd_nr',
+                       _('Set Hostapd Neighbor Report'),
+                       _('Method used to set Neighbor Report on AP'),
+               );
+               o.value('0', 'Disabled');
+               o.value('1', 'Static');
+               o.value('2', 'Dynamic');
+               o.placeholder = _('Default Value') + ': ' + 'Disabled';
+
+               o = s.option(form.Flag, 'use_station_count',
+                       _('Use Station Count'),
+                       _('Compare connected station counts when considering kicking'),
+               );
+
+               const bands = {
+                       '802_11g': _('2.4G Band Metric'),
+                       '802_11a': _('5G Band Metric'),
+               };
+
+               for (const k in bands) {
+                       s = m.section(form.NamedSection, k, 'metric',
+                               bands[k],
+                       );
+
+                       o = s.option(form.Value, 'ap_weight',
+                               _('Ap Weight'),
+                               _('Per AP weighting'),
+                       );
+                       o.placeholder = _('Default Value') + ': ' + '0';
+
+                       o = s.option(form.Value, 'chan_util',
+                               _('Channel Utilization'),
+                               _('Score increment if channel utilization is below chan_util_val'),
+                       );
+                       o.placeholder = _('Default Value') + ': ' + '0';
+
+                       o = s.option(form.Value, 'chan_util_val',
+                               _('Channel Utilization Value'),
+                               _('Upper threshold for good channel utilization'),
+                       );
+                       o.placeholder = _('Default Value') + '140';
+
+                       o = s.option(form.Value, 'ht_support',
+                               _('HT Support'),
+                               _('Score increment if HT is supported'),
+                       );
+                       o.placeholder = _('Default Value') + ': ' + '5';
+
+                       o = s.option(form.Value, 'initial_score',
+                               _('Initial Score'),
+                               _('Base score for AP based on operating band'),
+                       );
+                       o.placeholder = _('Default Value') + ': ' + k == '802_11g' ? '80' : '100';
+
+                       o = s.option(form.Value, 'low_rssi',
+                               _('Low RSSI'),
+                               _('Score addition when signal is below threshold'),
+                       );
+                       o.placeholder = _('Default Value') + ': ' + '-15';
+
+                       o = s.option(form.Value, 'low_rssi_val',
+                               _('Low RSSI Value'),
+                               _('Threshold for bad RSSI'),
+                       );
+                       o.placeholder = _('Default Value') + ': ' + '-80';
+
+                       o = s.option(form.Value, 'max_chan_util',
+                               _('Max Channel Utilization'),
+                               _('Score increment if channel utilization is above max_chan_util_val'),
+                       );
+                       o.placeholder = _('Default Value') + ': ' + '-15';
+
+                       o = s.option(form.Value, 'max_chan_util_val',
+                               _('Max Channel Utilization Value'),
+                               _('Lower threshold for bad channel utilization')
+                       );
+                       o.placeholder = _('Default Value') + ':' + '170';
+                       o = s.option(form.Value, 'no_ht_support',
+                               _('No HT Support'),
+                               _('Score increment if HT is not supported'),
+                       );
+                       o.placeholder = _('Default Value') + ': ' + '0';
+
+                       o = s.option(form.Value, 'no_vht_support',
+                               _('No VHT Support'),
+                               _('Score increment if VHT is not supported'),
+                       );
+                       o.placeholder = _('Default Value') + ': ' + '0';
+
+                       o = s.option(form.Value, 'rssi_center',
+                               _('RSSI Center'),
+                               _('Midpoint for weighted RSSI evaluation'),
+                       );
+                       o.placeholder = _('Default Value') + ': ' + '-70';
+
+                       o = s.option(form.Value, 'rssi',
+                               _('RSSI'),
+                               _('Score addition when signal exceeds threshold'),
+                       );
+                       o.placeholder = _('Default Value') + ': ' + '-15';
+
+                       o = s.option(form.Value, 'rssi_val',
+                               _('RSSI Value'),
+                               _('Threshold for a good RSSI'),
+                       );
+                       o.placeholder = _('Default Value') + ': ' + '-60';
+
+                       o = s.option(form.Value, 'rssi_weight',
+                               _('RSSI Weight'),
+                               _('Per dB increment for weighted RSSI evaluation'),
+                       );
+                       o.placeholder = _('Default Value') + ': ' + '0';
+
+                       o = s.option(form.Value, 'vht_support',
+                               _('VHT Support'),
+                               _('Score increment if VHT is supported'),
+                       );
+                       o.placeholder = _('Default Value') + ': ' + '5';
+               };
+
+               return m.render();
+       }
+});
diff --git a/applications/luci-app-dawn/htdocs/luci-static/resources/view/status/dawn/hearing_map.js b/applications/luci-app-dawn/htdocs/luci-static/resources/view/status/dawn/hearing_map.js
new file mode 100644 (file)
index 0000000..fdf840e
--- /dev/null
@@ -0,0 +1,105 @@
+'use strict';
+'require uci';
+'require view';
+'require dawn.dawn-common as dawn';
+
+return view.extend({
+       handleSaveApply: null,
+       handleSave: null,
+       handleReset: null,
+
+       load: function() {
+               return Promise.all([
+                       dawn.isDawnRPCAvailable().then(function(isAvailable) {
+                               return ( isAvailable ? dawn.callDawnGetHearingMap() : null )
+                       }),
+                       dawn.isDawnRPCAvailable().then(function(isAvailable) {
+                               return ( isAvailable ? dawn.callDawnGetNetwork() : null )
+                       }),
+                       dawn.callHostHints()
+               ]);
+       },
+
+       render: function(data) {
+
+               const dawnHearingMapData = data[0];
+               const dawnNetworkData = data[1];
+               const hostHintsData = data[2];
+
+               let accessPointsHintsData = {};
+               let connectedClients = {};
+               for (let network in dawnNetworkData) {
+                       connectedClients[network] = [];
+                       let aps = Object.entries(dawnNetworkData[network]).map(function(ap) {
+                               accessPointsHintsData[ap[0]] = {name: ap[1].hostname};
+                               let clientData = Object.entries(ap[1]);
+                               for (let i = 0; i < clientData.length; i++) {
+                                       if (typeof clientData[i][1] === 'object') {
+                                               connectedClients[network].push(clientData[i][0]);
+                                       }
+                               }
+                       });
+               }
+
+               if (!dawnHearingMapData || !dawnNetworkData) {
+                       return dawn.getDawnServiceNotRunningErrorMessage();
+               }
+
+               const body = E([
+                       E('h2', _('Hearing Map'))
+               ]);
+
+               for (let network in dawnHearingMapData) {
+                       
+                       body.appendChild(
+                               E('h3', 'SSID: ' + network)
+                       );
+
+                       let hearing_map_table = E('table', { 'class': 'table cbi-section-table' }, [
+                               E('tr', { 'class': 'tr table-titles' }, [
+                                       E('th', { 'class': 'th' }, _('Client')),
+                                       E('th', { 'class': 'th' }, _('Access Point')),
+                                       E('th', { 'class': 'th' }, _('Frequency')),
+                                       E('th', { 'class': 'th' }, E('span', { 'data-tooltip': _('High Throughput') }, [ _('HT') ])),
+                                       E('th', { 'class': 'th' }, E('span', { 'data-tooltip': _('Very High Throughput') }, [ _('VHT') ])),
+                                       E('th', { 'class': 'th' }, _('Signal')),
+                                       E('th', { 'class': 'th' }, E('span', { 'data-tooltip': _('Received Channel Power Indication') }, [ _('RCPI') ])),
+                                       E('th', { 'class': 'th' }, E('span', { 'data-tooltip': _('Received Signal to Noise Indicator') }, [ _('RSNI') ])),
+                                       E('th', { 'class': 'th' }, _('Channel Utilization')),
+                                       E('th', { 'class': 'th' }, _('Connected to Network')),
+                                       E('th', { 'class': 'th' }, _('Score'))
+                               ])
+                       ]);
+
+                       let clients = Object.entries(dawnHearingMapData[network]).map(function(client) {
+                               
+                               return Object.entries(client[1]).map(function(ap) {
+
+                                       if (ap[1].freq != 0) {
+                                               return [
+                                                       dawn.getHostnameFromMAC(hostHintsData, client[0]),
+                                                       dawn.getHostnameFromMAC(accessPointsHintsData, ap[0]),
+                                                       dawn.getFormattedNumber(ap[1].freq, 3, 1000) + ' GHz (' + _('Channel') + ': ' + dawn.getChannelFromFrequency(ap[1].freq) + ')',
+                                                       dawn.getAvailableText(ap[1].ht_capabilities && ap[1].ht_support),
+                                                       dawn.getAvailableText(ap[1].vht_capabilities && ap[1].vht_support),
+                                                       ap[1].signal,
+                                                       ap[1].rcpi,
+                                                       ap[1].rsni,
+                                                       dawn.getFormattedNumber(ap[1].channel_utilization, 2, 2.55) + '%',
+                                                       dawn.getYesText(connectedClients[network].includes(client[0])),
+                                                       ap[1].score
+                                               ]
+                                       }
+                                       return undefined;
+                               })
+                               
+                       }).flat();
+                       clients = clients.filter(client => client !== undefined);
+
+                       cbi_update_table(hearing_map_table, clients, E('em', _('No clients connected.')));
+
+                       body.appendChild(hearing_map_table);
+               }
+               return body;
+       }
+});
diff --git a/applications/luci-app-dawn/htdocs/luci-static/resources/view/status/dawn/network_overview.js b/applications/luci-app-dawn/htdocs/luci-static/resources/view/status/dawn/network_overview.js
new file mode 100644 (file)
index 0000000..a605858
--- /dev/null
@@ -0,0 +1,99 @@
+'use strict';
+'require uci';
+'require view';
+'require dawn.dawn-common as dawn';
+
+return view.extend({
+       handleSaveApply: null,
+       handleSave: null,
+       handleReset: null,
+
+       load: function() {
+               return Promise.all([
+                       dawn.isDawnRPCAvailable().then(function(isAvailable) {
+                               return ( isAvailable ? dawn.callDawnGetNetwork() : null );
+                       }),
+                       dawn.callHostHints()
+               ]);
+       },
+
+       render: function(data) {
+
+               const dawnNetworkData = data[0];
+               const hostHintsData = data[1];
+
+               if (!dawnNetworkData) {
+                       return dawn.getDawnServiceNotRunningErrorMessage();
+               }
+
+               const body = E([
+                       E('h2', _('Network Overview'))
+               ]);
+
+               let client_table = {};
+
+               for (let network in dawnNetworkData) {
+                       
+                       body.appendChild(
+                               E('h3', 'SSID: ' + network)
+                       );
+
+                       let ap_table = E('table', { 'class': 'table cbi-section-table' }, [
+                               E('tr', { 'class': 'tr table-titles' }, [
+                                       E('th', { 'class': 'th left cbi-section-actions' }, _('Access Point')),
+                                       E('th', { 'class': 'th left cbi-section-actions' }, _('Interface')),
+                                       E('th', { 'class': 'th left cbi-section-actions' }, _('MAC')),
+                                       E('th', { 'class': 'th left cbi-section-actions' }, _('Utilization')),
+                                       E('th', { 'class': 'th left cbi-section-actions' }, _('Frequency')),
+                                       E('th', { 'class': 'th left cbi-section-actions' }, _('Stations Connected')),
+                                       E('th', { 'class': 'th left cbi-section-actions' }, E('span', { 'data-tooltip': _('High Throughput') }, [ _('HT') ])),
+                                       E('th', { 'class': 'th left cbi-section-actions' }, E('span', { 'data-tooltip': _('Very High Throughput') }, [ _('VHT') ])),
+                                       E('th', { 'class': 'th center cbi-section-actions' }, _('Clients')),
+                               ])
+                       ]);
+
+                       let aps = Object.entries(dawnNetworkData[network]).map(function(ap) {
+                               client_table[ap[0]] = E('table', { 'class': 'table cbi-section-table', 'style': 'display: table' }, [
+                                       E('tr', { 'class': 'tr table-titles' }, [
+                                               E('th', { 'class': 'th' }, _('Client')),
+                                               E('th', { 'class': 'th' }, E('span', { 'data-tooltip': _('High Throughput') }, [ _('HT') ])),
+                                               E('th', { 'class': 'th' }, E('span', { 'data-tooltip': _('Very High Throughput') }, [ _('VHT') ])),
+                                               E('th', { 'class': 'th' }, _('Signal'))
+                                       ])
+                               ]);
+
+                               let clients = [];
+                               let clientData = Object.entries(ap[1]);
+                               for (let i = 0; i < clientData.length; i++) {
+                                       if (typeof clientData[i][1] === 'object') {
+                                               clients.push([
+                                                       dawn.getHostnameFromMAC(hostHintsData ,clientData[i][0]),
+                                                       dawn.getAvailableText(clientData[i][1].ht),
+                                                       dawn.getAvailableText(clientData[i][1].vht),
+                                                       clientData[i][1].signal
+                                               ]);
+                                       }
+                               }
+
+                               cbi_update_table(client_table[ap[0]], clients, E('em', _('No clients connected.')));
+                       
+                               return [
+                                       ap[1].hostname,
+                                       ap[1].iface,
+                                       ap[0],
+                                       dawn.getFormattedNumber(ap[1].channel_utilization, 2, 2.55) + '%',
+                                       dawn.getFormattedNumber(ap[1].freq, 3, 1000) + ' GHz (' + _('Channel') + ': ' + dawn.getChannelFromFrequency(ap[1].freq) + ')',
+                                       ap[1].num_sta,
+                                       dawn.getAvailableText(ap[1].ht_support),
+                                       dawn.getAvailableText(ap[1].vht_support),
+                                       ap[1].num_sta > 0 ? client_table[ap[0]] : E('em', { 'style': 'display: inline' }, _('No clients connected.'))
+                               ]
+                       });
+
+                       cbi_update_table(ap_table, aps, E('em', _('No access points available.')));
+                       
+                       body.appendChild(ap_table);
+               }
+               return body;
+       }
+});
index 96e88db39e12f9f74840d4996c2bf1f35074dfed..04ca7cecdbebb70afe3ec53e381fa921f67d3718 100644 (file)
@@ -1,5 +1,5 @@
 {
-       "admin/dawn/": {
+       "admin/status/dawn": {
                "title": "DAWN",
                "order": 60,
                "action": {
                }
        },
 
-       "admin/dawn/network_overview": {
+       "admin/status/dawn/network_overview": {
                "title": "Network Overview",
                "order": 1,
                "action": {
                        "type": "view",
-                       "path": "dawn/network_overview"
+                       "path": "status/dawn/network_overview"
                }
        },
 
-       "admin/dawn/hearing_map": {
+       "admin/status/dawn/hearing_map": {
                "title": "Hearing Map",
                "order": 2,
                "action": {
                        "type": "view",
-                       "path": "dawn/hearing_map"
+                       "path": "status/dawn/hearing_map"
+               }
+       },
+
+       "admin/network/dawn": {
+               "title": "DAWN",
+               "order": 60,
+               "action": {
+                       "type": "view",
+                       "path": "network/dawn"
+               },
+               "depends": {
+                       "acl": [ "luci-app-dawn" ]
                }
        }
 }